/* debugger.c
**
** Copyright 2006, Brian Swetland.  All rights reserved.
** See provided LICENSE file or http://frotz.net/LICENSE for details.
*/

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>

#include "debugger.h"

static unsigned
none_rd32(DebugIO *io, unsigned addr)
{
    ERROR("rd32() not supported by transport");
    return 0;
}

static void
none_wr32(DebugIO *io, unsigned addr, unsigned value)
{
    ERROR("wr32() not supported by transport");
}

void
default_rd32x(DebugIO *io, unsigned addr, unsigned *data, unsigned count)
{
    unsigned n;
    for(n = 0; n < count; n++){
        *data++ = io->_rd32(io, addr);
        if(io->error) return;
        addr += 4;
    }
}

void
default_wr32x(DebugIO *io, unsigned addr, unsigned *data, unsigned count)
{
    unsigned n;
    for(n = 0; n < count; n++){
        io->_wr32(io, addr, *data++);
        if(io->error) return;
        addr += 4;
    }
}

void
default_jump_to(DebugIO *io, unsigned addr)
{
    ERROR("jump_to() not supported by transport");
}

void
default_halt(DebugIO *io)
{
    ERROR("halt() not supported by transport");
}

static void
none_connect(DebugIO *io) 
{}

static void
none_disconnect(DebugIO *io) 
{}

static DebugIO DebugIO_None = {
    ._rd32 = none_rd32,
    ._wr32 = none_wr32,
    ._rd32x = default_rd32x,
    ._wr32x = default_wr32x,
    ._jump_to = default_jump_to,
    ._halt = default_halt,
    .connect = none_connect,
    .disconnect = none_disconnect,
    .name = "Disconnected",
    .ctxt = 0,
};

DebugIO *DEBUGIO = &DebugIO_None;

extern DebugIO DebugIO_ARM7_JTAG;
extern DebugIO DebugIO_USB;
extern DebugIO DebugIO_USB_SAMBA;

extern Command tbl_cmd_basic[];
static Command *tbl_cmd_io = 0;
static Command *tbl_cmd_target = 0;

static int _connect(DebugIO *io)
{
    io->connect(io);
    if(io->online) {
        DEBUGIO = io;
        tbl_cmd_io = DEBUGIO->commands;
        msg("[ connected to %s ]", io->name);
        return 1;
    }

    return 0;
}

void connect(void)
{
    if(DEBUGIO->online) return;
    
    if(_connect(&DebugIO_ARM7_JTAG)) return;
    if(_connect(&DebugIO_USB)) return;
    if(_connect(&DebugIO_USB_SAMBA)) return;
    
    IOERROR("[ not connected ]");
}

void disconnect(void)
{
    if(DEBUGIO->online) {
        DEBUGIO->disconnect(DEBUGIO);
        DEBUGIO = &DebugIO_None;
        tbl_cmd_io = 0;
    }
}

Value
arg_to_Value(const char *arg)
{
    if(arg[0] == '%') {
        return strtoul(arg + 1, 0, 2);
    }
    if(arg[0] == '#') {
        return strtoul(arg + 1, 0, 10);
    }
    return strtoul(arg, 0, 16);
}

Address
arg_to_Address(const char *arg)
{
        /* TODO: symbol table */
    return arg_to_Value(arg);
}

String
arg_to_String(const char *arg)
{
    return arg;
}

static int
_dispatch_command(int n, const char **args, Command *tbl)
{
    if(tbl == 0) return -1;
    
    while(tbl->name){
        if(!strcmp(args[0], tbl->name)) {
            tbl->func(n - 1, args + 1);
            return 0;
        }
        tbl++;
    }

    return -1;
}

static int
dispatch_command(int n, const char **args)
{
    if(_dispatch_command(n, args, tbl_cmd_target) == 0) {
        return 0;
    }
    if(_dispatch_command(n, args, tbl_cmd_io) == 0) {
        return 0;
    }
    if(_dispatch_command(n, args, tbl_cmd_basic) == 0) {
        return 0;
    }
    
    return -1;
}

void
_display_help(Command *tbl)
{
    if(tbl == 0) return;

    while(tbl->name){
        msg("%-16s %s", tbl->name, tbl->help);
        tbl++;
    }
}

int
invoke(const char *cmd, const char *fmt, ...)
{
    va_list ap;
    int n = 1;
    int notfound = -1;
    const char *arg[MAX_ARGS];
    char str[MAX_ARGS][32];
    
    if(cmd == 0) return -1;
    
    arg[0] = cmd;
    
    if(fmt != 0) {
        va_start(ap, fmt);
        
        while(*fmt){
            if(n == MAX_ARGS) return -1;
            
            switch(*fmt){
            case '-':
                notfound = 0;
                break;
            case 'i':
            case 'u':
                sprintf(str[n], "%u", va_arg(ap, unsigned));
                arg[n] = str[n];
                n++;
                break;
            case 's':
                arg[n++] = va_arg(ap, const char *);
                break;
            default:
                ERROR("unknown invoke() format '%c'", *fmt);
            }
            fmt++;            
        }
    }

    if(dispatch_command(n, arg)) {
        if(notfound) {
            ERROR("unknown command '%s'", arg[0]);
        }
        return notfound;
    }
    return DEBUGIO->error ? -1 : 0;
}

void
display_help(void)
{
    _display_help(tbl_cmd_basic);
    _display_help(tbl_cmd_io);
    _display_help(tbl_cmd_target);
}

void process_command(char *cmd)
{
    int n = 0;
    const char *arg[MAX_ARGS];

    msg("> %s", cmd);
    
process_args:
    while(*cmd){
        if(isspace(*cmd)){
            *cmd++;
            continue;
        }
        if(n == MAX_ARGS) break;
        arg[n++] = cmd++;
        while(*cmd){
            if(isspace(*cmd)){
                *cmd++ = 0;
                goto process_args;
            }
            cmd++;
        }
        break;
    }

    if(n == 0) return;
    
    if(!strcmp(arg[0], "help")){
        display_help();
        return;
    }

    DEBUGIO->error = 0;
    connect();
    if(!DEBUGIO->online) return;

    if(dispatch_command(n, arg)) {
        ERROR("unknown command '%s'", arg[0]);
    }
}

void msg(const char *fmt, ...)
{
    va_list ap;
    char buf[1024];

    va_start(ap, fmt);
    vsprintf(buf, fmt, ap);
    va_end(ap);
    ui_addline(buf);
}

void ERROR(const char *fmt, ...)
{
    va_list ap;
    char buf[1024];

    memcpy(buf,"ERROR: ", 7);
    
    va_start(ap, fmt);
    vsprintf(buf + 7, fmt, ap);
    va_end(ap);
    ui_addline(buf);

    DEBUGIO->error = 1;
}

void IOERROR(const char *fmt, ...)
{
    va_list ap;
    char buf[1024];

    memcpy(buf,"IOERROR: ", 9);
    
    va_start(ap, fmt);
    vsprintf(buf + 9, fmt, ap);
    va_end(ap);
    ui_addline(buf);

    DEBUGIO->error = 1;
    disconnect();
}

extern Command tbl_cmd_at91sam7[];

int main(int argc, char **argv)
{
    tbl_cmd_target = tbl_cmd_at91sam7;
    
    connect();
    ui_start();
    return 0;
}

void *load_file(const char *name, int *sz)
{
    int fd;
    char *data = 0;
    int size;

    fd = open(name, O_RDONLY);
    if(fd < 0) return 0;
    size = lseek(fd, 0, SEEK_END);
    if(size < 0) goto oops;
    if(lseek(fd, 0, SEEK_SET) <0) goto oops;
    if((data = (char*) malloc(size + 1024)) == 0) goto oops;
    if(read(fd, data, size) != size) goto oops;

    *sz = size;
    return (void*) data;

oops:
    close(fd);
    if(data) free(data);
    return 0;
}

